use super::xml;

#[derive(Debug, Clone, PartialEq)]
pub enum Event<'a> {
	Error(Error, (usize, usize)),

	Array,
	Dictionary,
	End,

	Key(&'a str),
	Boolean(bool),
	Data(&'a str),
	Date(&'a str),
	Integer(i128),
	Real(f64),
	String(&'a str),
	Uid(u64),
}
impl<'a> Event<'a> {
	pub fn as_key(&self) -> Option<&'a str> {
		match self {
			Self::Key(v) => Some(v),
			_ => None,
		}
	}
	pub fn as_str(&self) -> Option<&'a str> {
		match self {
			Self::String(v) => Some(v),
			_ => None,
		}
	}
	pub fn as_string(&self) -> Option<String> {
		self.as_str().map(|s| s.to_string())
	}
	pub fn as_integer(&self) -> Option<i128> {
		match self {
			Self::Integer(v) => Some(*v),
			_ => None,
		}
	}
	pub fn as_signed(self) -> Option<i64> {
		let value = self.as_integer()?;
		if value >= i128::from(i64::min_value()) && value <= i128::from(i64::max_value()) {
			Some(value as i64)
		} else {
			None
		}
	}
	pub fn as_unsigned(self) -> Option<u64> {
		let value = self.as_integer()?;
		if value >= 0 && value <= i128::from(u64::max_value()) {
			Some(value as u64)
		} else {
			None
		}
	}
	pub fn as_bool(self) -> Option<bool> {
		match self {
			Self::Boolean(v) => Some(v),
			_ => None,
		}
	}
	pub fn as_float(self) -> Option<f64> {
		match self {
			Self::Real(v) => Some(v),
			_ => None,
		}
	}
}

pub struct Parser<'a, 'l> {
	r: &'a mut xml::Parser<'l>,
}
impl<'a, 'l> Parser<'a, 'l> {
	#[inline]
	pub fn new(r: &'a mut xml::Parser<'l>) -> Self {
		Self { r }
	}
	fn read_content(&mut self) -> Result<&'l str, Error> {
		loop {
			match self.r.next().ok_or("expect content")? {
				xml::Event::Text(text) => return Ok(text),
				xml::Event::End(_) => return Ok(""),
				xml::Event::Error(e, _, _) => return Err(e.into()),
				_ => {}
			}
		}
	}
	pub fn read_next(&mut self) -> Result<Event<'l>, Error> {
		loop {
			match self.r.next().ok_or(Error::Eof)? {
				xml::Event::Start(s) => match s {
					"plist" => {}
					"array" => return Ok(Event::Array),
					"dict" => return Ok(Event::Dictionary),
					"key" => return Ok(Event::Key(self.read_content()?)),
					"data" => return Ok(Event::Data(self.read_content()?)),
					"date" => return Ok(Event::Date(self.read_content()?)),
					"integer" => return Ok(Event::Integer(parse_integer(self.read_content()?).map_err(Error::Integer)?)),
					"real" => return Ok(Event::Real(self.read_content()?.parse().map_err(Error::Real)?)),
					"string" => return Ok(Event::String(self.read_content()?)),
					"true" => return Ok(Event::Boolean(true)),
					"false" => return Ok(Event::Boolean(false)),
					_ => {}
				},
				xml::Event::End(s) => match s {
					"array" | "dict" => return Ok(Event::End),
					_ => {}
				},
				_ => {}
			}
		}
		fn parse_integer(s: &str) -> Result<i128, std::num::ParseIntError> {
			if s.starts_with("0x") {
				let s = s.trim_start_matches("0x");
				u64::from_str_radix(s, 16).map(Into::into)
			} else {
				Ok(match s.parse::<i64>() {
					Ok(v) => v.into(),
					Err(_) => s.parse::<u64>()?.into(),
				})
			}
		}
	}
}
impl<'a, 'l> Iterator for Parser<'a, 'l> {
	type Item = Event<'l>;
	fn next(&mut self) -> Option<Self::Item> {
		match self.read_next() {
			Ok(event) => Some(event),
			Err(error) => match error {
				Error::Eof => None,
				error => Some(Event::Error(error, self.r.pos())),
			},
		}
	}
}

#[derive(Debug, Clone, PartialEq)]
pub enum Error {
	Eof,
	Plist(&'static str),
	Xml(xml::Error),
	Integer(core::num::ParseIntError),
	Real(core::num::ParseFloatError),
}
impl core::error::Error for Error {}
impl core::fmt::Display for Error {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		match self {
			Error::Plist(s) => s.fmt(f),
			Error::Xml(error) => error.fmt(f),
			Error::Integer(error) => error.fmt(f),
			Error::Real(error) => error.fmt(f),
			Error::Eof => f.write_str("Eof"),
		}
	}
}
impl From<&'static str> for Error {
	fn from(value: &'static str) -> Self {
		Self::Plist(value)
	}
}
impl From<xml::Error> for Error {
	fn from(value: xml::Error) -> Self {
		Self::Xml(value)
	}
}

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn test_parse() {
		let mut binding = xml::Parser::new(
			r#"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>creator</key>
	<string>org.robofab.ufoLib</string>
	<key>formatVersion</key>
	<integer>3</integer>
	<key>formatVersionMinor</key>
	<integer>0</integer>
</dict>
</plist>"#,
		);
		let p = Parser::new(&mut binding);
		for e in p {
			println!("{:?}", e);
		}
	}
}
